﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using MiniStock.Entities.Common.Events;
using MiniStock.Infrastructure;
using MiniStock.Infrastructure.Configurator;
using MiniStock.Infrastructure.Queue;

namespace MiniStock.BusinessLogic.EventQueue.Queue
{
    internal class MemoryQueueImpl
    {
        object _queueLock = new object();
        private static int ticks = 0;
        SortedList<long, EventBase> _queue = new SortedList<long, EventBase>();
        readonly Dictionary<string, string> _entityHashMap = new Dictionary<string, string>();

        public EventBase Dequeue(string clientHash = null)
        {
            lock (_queueLock)
            {
                if (_queue.Count == 0) return null;

                var result =
                    _queue.FirstOrDefault(i =>
                        clientHash == null ||
                        i.Value is ProtocolEventBase ||
                        i.Value is EntityEventBase && HashMatch(((EntityEventBase)i.Value).GetEntityHash(), clientHash));

                if (result.Value == null) return null;

                if (result.Value is EntityEventBase)
                {
                    _entityHashMap[((EntityEventBase)result.Value).GetEntityHash()] = clientHash;
                }
                _queue.Remove(result.Key);
                TotalGetCount++;
                return result.Value;
            }
        }

        public void Enqueue(EventBase item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            lock (_queueLock)
            {
                if(item.EventDate == default(DateTime)) 
                    item.EventDate = DateTime.UtcNow;
                _queue.Add(ticks++, item);
                TotalAddCount++;
            }
        }

        public bool HasEvent()
        {
            lock (_queueLock)
            {
                return _queue.Count > 0;
            }
        }

        public IEnumerable<EventBase> AsEnumerable()
        {
            return _queue.Values.AsEnumerable();
        }

        public Int64 TotalAddCount { get; set; }
        public Int64 TotalGetCount { get; set; }

        public int Count()
        {
            return _queue.Count;
        }

        private bool HashMatch(string entityHash, string clientId)
        {
            if (_entityHashMap.ContainsKey(entityHash))
                return _entityHashMap[entityHash] == clientId;

            return true;
        }
    }

    public class MemoryQueue: IEventQueue
    {
        internal static readonly Dictionary<string, MemoryQueueImpl> queues = new Dictionary<string, MemoryQueueImpl>();

        #region IEventQueue Members

        public string Address { get; set; }

        public EventBase Dequeue(string clientHash =null)
        {
            return queues[Address].Dequeue(clientHash);
        }

        public void Enqueue(EventBase item)
        {
            queues[Address].Enqueue(item);
        }

        public bool HasEvent()
        {
            return queues[Address].HasEvent();
        }

        public void Configure(IEventQueueConfigurator configurator, string queueName)
        {
            lock(queues) {
                this.Address = configurator.GetQueueAddress(queueName);
                if (!queues.ContainsKey(Address))
                    queues[Address] = new MemoryQueueImpl();
            }
        }

        public IEnumerable<EventBase> AsEnumerable()
        {
            return queues[Address].AsEnumerable();
        }

        public int Count()
        {
            return queues[Address].Count();
        }

        #endregion

        public static List<Metric> CollectMetrics()
        {
            return queues.Select(q => new Metric() {Name = "Queue length: " + q.Key, Value = q.Value.Count(),
                                                    AccumulatedAdd = q.Value.TotalAddCount,
                                                    AccumulatedGet = q.Value.TotalGetCount, 
            }).ToList();
        }
    }
}
